package org.eweb4j.orm.config;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import org.eweb4j.cache.ORMConfigBeanCache;
import org.eweb4j.config.LogFactory;
import org.eweb4j.orm.PropType;
import org.eweb4j.orm.annotation.Ignore;
import org.eweb4j.orm.config.bean.ORMConfigBean;
import org.eweb4j.orm.config.bean.Property;
import org.eweb4j.util.ClassUtil;
import org.eweb4j.util.FileUtil;
import org.eweb4j.util.ReflectUtil;

/**
 * Persistent obj annotation read to cache
 * 
 * @author weiwei
 * 
 */
public class PojoAnnotationConfig {
	/**
	 * 读取注解，并存入配置缓存中
	 * 
	 * @param scanPackage
	 * @return
	 */
	public static String readAnnotation(List<String> scanPackages) {
		String error = null;

		if (scanPackages == null)
			return null;

		for (String scanPackage : scanPackages) {
			if (scanPackage == null || scanPackage.length() == 0)
				continue;

			String classDir = FileUtil
					.getTopClassPath(PojoAnnotationConfig.class);
			scanDir(scanPackage, classDir);

			String classDir2 = FileUtil.getClassPath("classes");
			scanDir(scanPackage, classDir2);

			// 扫描jar包
			String jarPath = FileUtil.getJarPath();
			try {
				scanPackage(jarPath, scanPackage);
			} catch (Error e) {
				continue;
			} catch (Exception e) {
				continue;
			}
		}

		return error;
	}

	private static void scanDir(String scanPackage, String classDir) {
		File dir = null;

		if (".".equals(scanPackage)) {
			scanPackage = "";
			dir = new File(classDir);
		} else
			dir = new File(classDir + File.separator
					+ scanPackage.replace(".", File.separator));

		if (dir.isDirectory())
			scanPackage(dir, scanPackage);
	}

	/**
	 * 扫描包目录下的所有文件
	 * 
	 * @param dir
	 * @param actionPackage
	 * @throws Exception
	 */
	private static void scanPackage(File dir, String actionPackage) {
		if (!dir.isDirectory())
			return;
		LogFactory.getORMLogger("INFO").write("scan " + dir);
		File[] files = dir.listFiles();

		if (files == null || files.length == 0)
			return;

		for (File file : files) {
			// 递归
			if (file.isDirectory()) {
				if (actionPackage.length() == 0)
					scanPackage(file, file.getName());
				else
					scanPackage(file, actionPackage + "." + file.getName());
			} else if (file.isFile()) {
				String fname = file.getName();
				if (!fname.endsWith(".class"))
					continue;

				String clsName = actionPackage + "."
						+ fname.substring(0, fname.lastIndexOf("."));
				if (clsName == null || "".equals(clsName))
					continue;
				try {
					handlePojoClass(clsName);
				} catch (Exception e) {

				}
			}
		}
	}

	/**
	 * scan package by jars
	 * 
	 * @param jarsParentDirPath
	 * @param packageName
	 * @throws Exception
	 */
	public static void scanPackage(String jarsParentDirPath, String packageName) {
		String path = jarsParentDirPath;
		LogFactory.getORMLogger("INFO").write("scan " + path + " for jars");
		File[] ff = new File(path).listFiles();
		if (ff == null)
			return;

		for (File f : ff) {
			ZipInputStream zin = null;
			ZipEntry entry = null;
			try {
				zin = new ZipInputStream(new FileInputStream(f));
				entry = zin.getNextEntry();
			} catch (Error e) {
				continue;
			} catch (Exception e) {
				continue;
			}

			while (entry != null) {
				try {
					entry = zin.getNextEntry();

					String entryName = entry.getName().replace('/', '.');
					if (".".equals(packageName)
							|| entryName.startsWith(packageName))
						handlePojoClass(entryName.replace(".class", ""));

					zin.closeEntry();
				} catch (Error e) {
					continue;
				} catch (Exception e) {
					continue;
				}
			}

			try {
				zin.close();
			} catch (Error e) {
				continue;
			} catch (Exception e) {
				continue;
			}

		}
	}

	/**
	 * 
	 * @param clsName
	 * @throws Exception
	 */
	public static void handlePojoClass(String clsName) {
		Class<?> clazz = null;
		ReflectUtil ru;
		try {
			clazz = Class.forName(clsName);
		} catch (Error e) {
			return;
		} catch (Exception e) {
			return;
		}

		if (clazz == null)
			return;

		Entity entity = clazz.getAnnotation(Entity.class);
		if (entity == null && !clsName.endsWith("PO")
				&& !clsName.endsWith("POJO") && !clsName.endsWith("Entity")
				&& !clsName.endsWith("Model")) {
			return;
		}
		Table tableAnn = clazz.getAnnotation(Table.class);
		String table = tableAnn == null ? "" : tableAnn.name();
		table = "".equals(table.trim()) ? clazz.getSimpleName()
				.replace("PO", "").replace("POJO", "").replace("Entity", "")
				.replace("Model", "") : table;
		ORMConfigBean ormBean = new ORMConfigBean();
		ormBean.setClazz(clazz.getName());
		ormBean.setId(clazz.getSimpleName());
		ormBean.setTable(table);
		try {
			ru = new ReflectUtil(clazz);
		} catch (Error e) {
			return;
		} catch (Exception e) {
			return;
		}

		List<Property> pList = new ArrayList<Property>();
		for (Field f : ru.getFields()) {
			String name = f.getName();
			Method getter = ru.getGetter(name);
			if (getter == null)
				continue;

			Ignore igAnn = f.getAnnotation(Ignore.class);
			if (igAnn != null)
				continue;

			OneToMany manyAnn = getter.getAnnotation(OneToMany.class);
			if (manyAnn != null)
				continue;
			else {
				manyAnn = f.getAnnotation(OneToMany.class);
				if (manyAnn != null)
					continue;
			}

			ManyToMany mmAnn = getter.getAnnotation(ManyToMany.class);
			if (mmAnn != null)
				continue;
			else {
				mmAnn = f.getAnnotation(ManyToMany.class);
				if (mmAnn != null)
					continue;
			}

			Property p = new Property();

			Id idAnn = getter.getAnnotation(Id.class);
			if (idAnn == null)
				idAnn = f.getAnnotation(Id.class);

			if (idAnn != null) {
				p.setAutoIncrement("1");
				p.setPk("1");
			}

			Column colAnn = getter.getAnnotation(Column.class);
			if (colAnn == null) {
				colAnn = f.getAnnotation(Column.class);
			}

			String column = colAnn == null ? "" : colAnn.name();
			column = "".equals(column.trim()) ? name : column;
			p.setName(name);
			p.setColumn(column);
			p.setType(f.getType().getName());

			if (ClassUtil.isPojo(f.getType())) {
				OneToOne oneAnn = getter.getAnnotation(OneToOne.class);
				if (oneAnn == null) {
					oneAnn = f.getAnnotation(OneToOne.class);
				}

				if (oneAnn != null) {
					p.setType(PropType.ONE);
					p.setColumn(oneAnn.mappedBy());
				}
			}

			pList.add(p);
		}

		ormBean.setProperty(pList);
		ORMConfigBeanCache.add(clazz, ormBean);
	}
}
